import { ESCesiumViewer } from '@czmSrc/ESCesiumViewer';
import { createNextAnimateFrameEvent, createProcessingFromAsyncFunc, extendClassProps, getDomEventCurrentTargetPos, Processing, react, reactArray, reactArrayWithUndefined, ReactivePropsToNativePropsAndChanged, ReactiveVariable, step, track } from 'xbsj-base';
import * as Cesium from 'cesium';
import { computePickingInfoWithCoordinates, CoordinatesComputingPickingInfo, CurrentScale, fromCartographic, geoNeareastPointOnRhumbLine, pickHeightPosition, pickVirtualEarth, positionFromCartesian, positionToCartesian, toCartesian } from '@czmSrc/utils';
import { geoRhumbDestination, geoRhumbDistance, geoRhumbHeading, ESJVector3D, getDistancesFromPositions, geoPointToLineDistance, geoDistance } from 'earthsdk3';
import { PointEditing } from '../index';
import { GeoScale } from './GeoScale';

type OpInfoType = {
    step: ReactiveVariable<boolean>,//停止
    moved: ReactiveVariable<boolean>,
    constraintMode: ReactiveVariable<CoordinatesComputingPickingInfo['constraintMode']>;//运行模式
    originPosition: ReactiveVariable<ESJVector3D>;//原点位置
    originHeading: ReactiveVariable<number>;//原点朝向
    originDimensions: ReactiveVariable<ESJVector3D>;//原点尺寸
    draggingStartPosition: ReactiveVariable<ESJVector3D>;//拖拽开始位置
    movingPosition: ReactiveVariable<ESJVector3D>;//移动位置
    targetPosition: ReactiveVariable<ESJVector3D>;//目标位置
    targetHeading: ReactiveVariable<number>,// 目标朝向
}
export class ScaleEditing extends PointEditing {
    private _lastPosition = undefined as ESJVector3D | undefined;
    private _opInfo: OpInfoType = {
        step: this.ad(react(false)),
        moved: this.ad(react(false)),
        constraintMode: this.disposeVar(react<CoordinatesComputingPickingInfo['constraintMode']>('none')),
        originPosition: this.disposeVar(reactArray<ESJVector3D>([0, 0, 0])),
        originHeading: this.disposeVar(react(0)),
        originDimensions: this.disposeVar(reactArray<ESJVector3D>([1, 1, 1])),
        draggingStartPosition: this.disposeVar(reactArray<ESJVector3D>([0, 0, 0])),
        movingPosition: this.disposeVar(reactArray<ESJVector3D>([0, 0, 0])),
        targetPosition: this.disposeVar(reactArray<ESJVector3D>([0, 0, 0])),
        targetHeading: this.disposeVar(react(0)),
    };
    private _scaleAxis;
    get scaleAxis() { return this._scaleAxis; }
    constructor(czmViewer: ESCesiumViewer) {
        super();
        const viewer = czmViewer.viewer;
        if (!viewer) return;
        const { scene } = viewer;
        // 根据相机视角控制坐标轴缩放
        this.ad(new CurrentScale(czmViewer, this.positionReact, this._opInfo.originDimensions, this.axisPixelSize));
        // 双击取消编辑
        this.ad(czmViewer.dblclickEvent.don(() => {
            this.enabled = false;
        }))
        this._scaleAxis = {
            zAxis: this._createScaleAxis(czmViewer, 'zAxis'),
            yAxis: this._createScaleAxis(czmViewer, 'yAxis'),
            xAxis: this._createScaleAxis(czmViewer, 'xAxis'),
        }
        {
            const updateColor = () => {
                if (!this._scaleAxis) return;
                const { zAxis, yAxis, xAxis } = this._scaleAxis;
                const constraintMode: CoordinatesComputingPickingInfo['constraintMode'] = this._opInfo.constraintMode.value;
                const transparent = this._opInfo.step

                zAxis.color = [0, 0, 1, .99];
                yAxis.color = [0, 1, 0, .99];
                xAxis.color = [1, 0, 0, .99];

                if (constraintMode === 'x') {
                    xAxis.color = transparent ? [1, 1, 0, .8] : [1, 1, 0, .99];
                } else if (constraintMode === 'y') {
                    yAxis.color = transparent ? [1, 1, 0, .8] : [1, 1, 0, .99];
                } else if (constraintMode === 'z') {
                    zAxis.color = transparent ? [1, 1, 0, .8] : [1, 1, 0, .99];
                }
            }
            updateColor();
            this.dispose(this._opInfo.constraintMode.changed.disposableOn(updateColor));
            this.dispose(this._opInfo.step.changed.disposableOn(updateColor));
        }
        {
            const update = () => {
                if (!this._scaleAxis) return;
                const { position = ScaleEditing.defaults.position } = this;
                const { zAxis, yAxis, xAxis } = this._scaleAxis;
                zAxis.startPosition = yAxis.startPosition = xAxis.startPosition = position;
                const [l, b, h] = position;
                zAxis.stopPosition = [l, b, h + this._opInfo.originDimensions.value[2]];
                const xd = geoRhumbDestination(position, this._opInfo.originDimensions.value[0], 90);
                xd && (xAxis.stopPosition = xd);
                const yd = geoRhumbDestination(position, this._opInfo.originDimensions.value[1], 0);
                yd && (yAxis.stopPosition = yd);
            }
            update();
            const event = this.ad(createNextAnimateFrameEvent(
                this.positionChanged,
                this._opInfo.originDimensions.changed
            ))
            this.ad(event.don(update));
        }
        {
            // 判断是否移动了
            const updateMoved = () => {
                if (this._opInfo.constraintMode.value === 'none') {
                    this._opInfo.moved.value = false;
                    return;
                }
                if (this._opInfo.originHeading.value !== this._opInfo.targetHeading.value) {
                    this._opInfo.moved.value = true;
                    return;
                }
                if (!this._opInfo.originPosition.value.every((e, i) => e === this._opInfo.targetPosition.value[i])) {
                    this._opInfo.moved.value = true;
                    return;
                }
            };
            updateMoved();
            this.dispose(this._opInfo.originHeading.changed.disposableOn(updateMoved));
            this.dispose(this._opInfo.originPosition.changed.disposableOn(updateMoved));
            this.dispose(this._opInfo.targetHeading.changed.disposableOn(updateMoved));
            this.dispose(this._opInfo.targetPosition.changed.disposableOn(updateMoved));
        }
        const pickingXYProcessing = this._pickingXYProcessing(czmViewer);
        const pickingZProcessing = this._pickingZProcessing(czmViewer);
        const mainProcessing = this.disposeVar(createProcessingFromAsyncFunc(async cancelsManager => {
            do {
                this._opInfo.step.value = true;
                this._opInfo.constraintMode.value = 'none';
                cancelsManager.disposer.dispose(() => {
                    this._opInfo.constraintMode.value = 'none';
                });
                // 1 获取限制轴信息，鼠标按下时进入下一步
                await step(cancelsManager, async cancelsManager => {
                    const updatePosition = () => {
                        const tempPos = [...(this.position ?? ScaleEditing.defaults.position)] as ESJVector3D;
                        czmViewer.editingHeightOffset && (tempPos[2] -= czmViewer.editingHeightOffset);
                        this._opInfo.targetPosition.value = this._opInfo.originPosition.value = tempPos;
                    };
                    cancelsManager.disposer.dispose((updatePosition(), this.positionChanged.disposableOn(updatePosition)));
                    const pickingInfo: CoordinatesComputingPickingInfo = {
                        constraintMode: 'none',
                        startDragPos: [0, 0, 0],
                    };
                    const updatePickingInfo = (pointerEvent: MouseEvent) => {
                        if (!this.position) return;
                        const ci = {
                            position: this.position,
                            dimensions: this._opInfo.originDimensions.value,
                            heading: 0
                        };
                        computePickingInfoWithCoordinates(
                            pointerEvent,
                            scene,
                            ci,
                            this.axisSnapPixelSize,
                            pickingInfo,
                        );
                        this._opInfo.constraintMode.value = pickingInfo.constraintMode;
                        this._opInfo.movingPosition.value = this._opInfo.draggingStartPosition.value = pickingInfo.startDragPos;
                    }
                    cancelsManager.disposer.dispose(czmViewer.pointerMoveEvent.disposableOn((pointerEvent) => {
                        pointerEvent.pointerEvent && updatePickingInfo(pointerEvent.pointerEvent);
                    }));
                    await step(cancelsManager, cancelsManager => {
                        return new Promise<void>((resolve, reject) => {
                            cancelsManager.disposer.dispose(reject);
                            cancelsManager.disposer.dispose(czmViewer.pointerDownEvent.don(pointerEvent => {
                                pointerEvent.pointerEvent && updatePickingInfo(pointerEvent.pointerEvent);
                                if (pointerEvent.pointerEvent && pointerEvent.pointerEvent.button === 0
                                    && pickingInfo.constraintMode !== 'none') {
                                    resolve();
                                }
                            }))
                        });
                    });
                });

                // 2 移动坐标轴过程中
                this._opInfo.step.value = false;
                await step(cancelsManager, async cancelsManager => {
                    {
                        czmViewer.incrementDisabledInputStack();
                        cancelsManager.disposer.dispose(() => czmViewer.decrementDisabledInputStack());
                    }
                    if (['x', 'y'].includes(this._opInfo.constraintMode.value)) {
                        const inverseHeading = geoRhumbHeading(this._opInfo.draggingStartPosition.value, this._opInfo.originPosition.value);
                        const inverseDistance = geoRhumbDistance(this._opInfo.draggingStartPosition.value, this._opInfo.originPosition.value);
                        pickingXYProcessing.restart(undefined, inverseHeading, inverseDistance);
                        cancelsManager.disposer.dispose(() => pickingXYProcessing.cancel());
                    } else if (this._opInfo.constraintMode.value === 'z') {
                        pickingZProcessing.restart();
                        cancelsManager.disposer.dispose(() => pickingZProcessing.cancel());
                    }

                    await step(cancelsManager, cancelsManager => {
                        return new Promise<void>((resolve, reject) => {
                            cancelsManager.disposer.dispose(reject);
                            cancelsManager.disposer.dispose(czmViewer.pointerUpEvent.don(() => { resolve() }));
                            cancelsManager.disposer.dispose(czmViewer.pointerOutEvent.don(() => { resolve() }));
                        })
                    });
                    this._lastPosition = undefined;
                });
            } while (true);
        }));
        const updateMainProcessing = () => {
            if (this.enabled) {
                mainProcessing.restart();
            } else {
                mainProcessing.isRunning && mainProcessing.cancel();
            }
        }
        updateMainProcessing();
        this.dispose(this.enabledChanged.disposableOn(updateMainProcessing));
    }
    private _createScaleAxis(czmViewer: ESCesiumViewer, type: string) {
        const scaleAxis = this.ad(new GeoScale(czmViewer));
        scaleAxis.width = 10;
        this.ad(track([scaleAxis, 'show'], [this, 'enabled']));
        return scaleAxis;
    }
    private _pickingXYProcessing = (czmViewer: ESCesiumViewer,) => {
        return this.disposeVar(new Processing<void, [inverseHeading: number, inverseDistance: number]>((
            _,
            inverseHeading,
            inverseDistance
        ) => {
            const movingPosition: ESJVector3D = [0, 0, 0];
            const movingCartographic = new Cesium.Cartographic();

            return czmViewer.pointerMoveEvent.disposableOn((pointerEvent) => {
                if (!pointerEvent.pointerEvent || !czmViewer.viewer) return
                const screenPosition = toCartesian(getDomEventCurrentTargetPos(pointerEvent.pointerEvent));
                if (!pickVirtualEarth(czmViewer.viewer.scene, screenPosition, this._opInfo.originPosition.value[2], movingCartographic)) {
                    return;
                }
                fromCartographic(movingCartographic, movingPosition);

                if (this._opInfo.constraintMode.value === 'x') {
                    geoNeareastPointOnRhumbLine(this._opInfo.originPosition.value, this._opInfo.originHeading.value, movingPosition, movingPosition);
                } else if (this._opInfo.constraintMode.value === 'y') {
                    geoNeareastPointOnRhumbLine(this._opInfo.originPosition.value, this._opInfo.originHeading.value + 90, movingPosition, movingPosition);
                } else if (this._opInfo.constraintMode.value === 'xy') {
                } else {
                    console.warn(`should not be here!`);
                }
                this._opInfo.movingPosition.value = movingPosition;
                if (!this._lastPosition) this._lastPosition = [...this._opInfo.draggingStartPosition.value];
                const stopPosition: ESJVector3D = [0, 0, 0];
                geoRhumbDestination(movingPosition, inverseDistance, inverseHeading, stopPosition);
                this._opInfo.targetPosition.value = stopPosition;
                let scale0 = this.scale[0];
                let scale1 = this.scale[1];
                const add = geoDistance(stopPosition, this._opInfo.originPosition.value) - geoDistance(this._lastPosition, this._opInfo.originPosition.value);
                this._lastPosition = [...stopPosition];
                const step = this._opInfo.originDimensions.value[0] / 100;
                if (add != 0 && this._opInfo.constraintMode.value === 'y') add > 0 ? scale0 += step : scale0 -= step;
                if (add != 0 && this._opInfo.constraintMode.value === 'x') add > 0 ? scale1 += step : scale1 -= step;
                if (scale0 <= 0) scale0 += step;
                if (scale1 <= 0) scale1 += step;
                this.scale = [(scale0 ?? 1), (scale1 ?? 1), this.scale[2] ?? 1];
            });
        }));
    }
    private _pickingZProcessing = (czmViewer: ESCesiumViewer) => {
        return this.disposeVar(new Processing(() => {
            const movingCartesian = new Cesium.Cartesian3();
            const movingPosition: ESJVector3D = [0, 0, 0];
            return czmViewer.pointerMoveEvent.disposableOn(pointerEvent => {
                if (!pointerEvent.pointerEvent || !czmViewer.viewer) return
                const screenPosition = toCartesian(getDomEventCurrentTargetPos(pointerEvent.pointerEvent));
                if (!pickHeightPosition(czmViewer.viewer.scene, positionToCartesian(this._opInfo.originPosition.value), screenPosition, movingCartesian)) {
                    return;
                }
                if (!this._lastPosition) this._lastPosition = [...this._opInfo.draggingStartPosition.value];
                const diffHeight = this._opInfo.originPosition.value[2] - this._opInfo.draggingStartPosition.value[2];
                if (positionFromCartesian(movingCartesian, movingPosition)) {
                    this._opInfo.movingPosition.value = movingPosition;
                    const op = this._opInfo.originPosition.value;
                    this._opInfo.targetPosition.value = [op[0], op[1], movingPosition[2] + diffHeight];
                    let scale2 = this.scale[2];
                    const subtract = movingPosition[2] - this._lastPosition[2];
                    this._lastPosition = [...movingPosition];
                    const step = this._opInfo.originDimensions.value[0] / 100;
                    if (subtract != 0) subtract > 0 ? scale2 += step : scale2 -= step;
                    if (scale2 <= 0) scale2 += step;
                    this.scale = [this.scale[0] ?? 1, this.scale[1] ?? 1, scale2 ?? 1];
                }
            });
        }));
    }
}
export namespace ScaleEditing {
    export const createDefaultProps = () => ({
        ...PointEditing.createDefaultProps(),
        scale: reactArray<ESJVector3D>([1, 1, 1]), // [1,1,1]
        axisPixelSize: 100,
        axisSnapPixelSize: 5,
    });
}
extendClassProps(ScaleEditing.prototype, ScaleEditing.createDefaultProps);
export interface ScaleEditing extends ReactivePropsToNativePropsAndChanged<ReturnType<typeof ScaleEditing.createDefaultProps>> { }